# -*- coding: utf-8 -*-

import time
import logging
import traceback

import redis
import msgpack

from odoo.http import request

from ..meta import Singleton
from ..exceptions import WechatMiniAppException
from .constants import FrontendKeys, WechatAppKeys

logger = logging.getLogger(__name__)


class RedisClient(object, metaclass=Singleton):
    def __init__(self):
        self.host = request.env['ir.config_parameter'].sudo().get_param('wechat.redis_host') or None
        self.port = int(request.env['ir.config_parameter'].sudo().get_param('wechat.redis_port')) or 6379
        self.db = int(request.env['ir.config_parameter'].sudo().get_param('wechat.redis_db')) or 0
        self.password = request.env['ir.config_parameter'].sudo().get_param('wechat.password') or None
        self.user_expiration = \
            int(request.env['ir.config_parameter'].sudo().get_param('wechat.user_expiration')) or 60 * 60 * 24 * 2
        self.formid_expiration = \
            int(request.env['ir.config_parameter'].sudo().get_param('wechat.formid_expiration')) or 60 * 60 * 24 * 7 - 200
        self.oauth_redirect_token_expiration = 60 * 60

        self.pool = redis.ConnectionPool(
            host=self.host,
            port=self.port,
            db=self.db,
            password=self.password,
        )
        try:
            self.client = redis.StrictRedis(connection_pool=self.pool)
            self.client.ping()
        except redis.exceptions.ConnectionError:
            logger.error(
                'Redis Connection error: %s',
                traceback.format_exc(),
            )
            self.client = None
            raise WechatMiniAppException(
                errcode=203001,
            )

    def login_user(self, user):
        try:
            uid = user.id
            key = FrontendKeys.user.format(uid)
            payload = msgpack.dumps(user.redis_payload)
            self.client.setex(key, self.user_expiration, payload)
        except:
            logger.error(
                'Error while persisting user info to redis. User: %s Err: %s',
                user.id,
                traceback.format_exc(),
            )
        else:
            logger.debug(
                'Persisted user info to redis. User: %s',
                user.id,
            )

    def load_user(self, uid):
        try:
            key = FrontendKeys.user.format(uid)
            info = self.client.get(key)
            if info is not None:
                info = msgpack.loads(info, raw=False)
        except:
            logger.error(
                'Error while loading user from redis. User: %s Err: %s',
                uid,
                traceback.format_exc(),
            )
            return None
        else:
            logger.debug(
                'Loaded user info from redis. User: %s',
                uid
            )
            return info

    def load_access_token(self):
        try:
            key = WechatAppKeys.access_token
            access_token = self.client.get(key)
            if access_token is not None:
                access_token = access_token.decode('utf8')
        except:
            logger.error(
                'Error while loading access token from redis: %s',
                traceback.format_exc(),
            )
            raise WechatMiniAppException(
                errcode=304051,
            )
        else:
            return access_token

    def persist_access_token(self, access_token, expiration):
        try:
            key = WechatAppKeys.access_token
            self.client.setex(key, expiration, access_token)
        except:
            logger.error(
                'Error while persist wechat access token. access_token: %s Err: %s',
                access_token,
                traceback.format_exc(),
            )
            raise WechatMiniAppException(
                errcode=304052,
            )

    def cache_form_ids(self, openid, form_ids):
        logger.debug('Caching form ids for %s -> %s', openid, form_ids)
        try:
            key = WechatAppKeys.wechat_formid.format(openid)
            expires_at = int(time.time()) + self.formid_expiration
            pipe = self.client.pipeline()
            for form_id in form_ids:
                pipe.zadd(
                    key,
                    expires_at,
                    form_id
                )
            pipe.execute()
        except:
            logger.error(
                'Error while caching form_ids to redis. openid: %s form_ids: %s Err: %s',
                openid,
                form_ids,
                traceback.format_exc(),
            )
            return False
        else:
            logger.debug(
                'Cached form_ids for %s -> %s',
                openid,
                form_ids,
            )
            return True

    def get_formid(self, openid):
        logger.debug('Retrieving formid for %s', openid)
        try:
            now = int(time.time())
            key = WechatAppKeys.wechat_formid.format(openid)
            removed = self.client.zremrangebyscore(key, 0, now)
            if removed:
                logger.info(
                    'Removed %s expired formids for %s',
                    removed,
                    openid
                )
            formid = self.client.zrange(key, 0, 0)
            if formid:
                formid = formid[0].decode('utf-8')
                removed = self.client.zrem(key, formid)
                if removed:
                    logger.info(
                        'Popped retrieved formid for: %s -> %s',
                        openid,
                        formid,
                    )
            else:
                formid = None
        except:
            logger.error(
                'Error while retrieving formid for %s Err: %s',
                openid,
                traceback.format_exc(),
            )
            return None
        else:
            logger.debug(
                'Retrieved formid for %s formid: %s',
                openid,
                formid,
            )
            return formid

    def get_users_has_formid(self):
        try:
            key = WechatAppKeys.wechat_formid.format('*')
            keys = self.client.keys(key)
            openids = []
            for k in keys:
                try:
                    k = k.decode('utf-8')
                    openid = k.split(':', 1)[1]
                    openids.append(openid)
                except:
                    pass
        except:
            logger.error(
                'Error while retrieving user with formid Err: %s',
                traceback.format_exc(),
            )
            return []
        else:
            return openids

    def persist_oauth_redirect_token(self, token, url):
        try:
            key = WechatAppKeys.oauth_redirect_token.format(token)
            self.client.setex(key, self.oauth_redirect_token_expiration, url)
        except:
            logger.error(
                'Error while persisting oauth redirect token. token: %s url: %s Err: %s',
                token,
                url,
                traceback.format_exc(),
            )
            return False
        else:
            return True

    def retrieve_oauth_redirect_url(self, token):
        try:
            key = WechatAppKeys.oauth_redirect_token.format(token)
            url = self.client.get(key)
            if url is not None:
                url = url.decode('utf8')
        except:
            logger.error(
                'Error while retrieving oauth redirect url. token: %s Err: %s',
                token,
                traceback.format_exc(),
            )
            return None
        else:
            return url
